package org.harsh.transmission.server;

import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysDeptService;
import io.jpower.kcp.netty.ChannelOptionHelper;
import io.jpower.kcp.netty.UkcpChannelOption;
import io.jpower.kcp.netty.UkcpServerChannel;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.bootstrap.UkcpServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.InternetProtocolFamily;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.traffic.ChannelTrafficShapingHandler;
import lombok.extern.slf4j.Slf4j;
import org.harsh.dispatch.CommVars;
import org.harsh.transmission.common.*;
import org.harsh.transmission.common.codec.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PreDestroy;
import java.net.*;
import java.util.Enumeration;

@Slf4j
@Component
public class NodeServer {
//    EventLoopGroup bossGroup = Epoll.isAvailable()? new EpollEventLoopGroup(1): new NioEventLoopGroup(1);
//    EventLoopGroup workGroup = Epoll.isAvailable()? new EpollEventLoopGroup(): new NioEventLoopGroup();
    @Autowired
    private ISysDeptService deptService;

    @Autowired
    private ServerChannelInitilizer serverChannelInitilizer;

    @Autowired
    private KcpServerChannelInitilizer kcpServerChannelInitilizer;

    @Autowired
    private PlainMsgCodec plainMsgCodec;

    @Autowired
    private FileMsgCodec fileMsgCodec;

    @Autowired
    private ISysConfigService sysConfigService;

    public static SysDept localDept;

    /**
     * 1、通过TCP进行协商密钥；
     * 2、通过TCP协商传输参数；
     * 3、通过指定协议进行数据传输；（网络状态检测）
     */
    public void start() {
        log.info("NettyServer start and listening tcp connection.");
        SysDept sysDept = deptService.loadLocalDept();
        NodeServer.localDept = sysDept;
        // 获取本地监听地址和端口
        String host = sysDept.getLocalAddr();
        int port = sysDept.getLocalPort();
        ServerBootstrap bootstrap = new ServerBootstrap();
        ChannelFuture future = null;
        bootstrap.group(CommVars.bossGroup, CommVars.workGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(serverChannelInitilizer)
                .option(ChannelOption.SO_BACKLOG, 128)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .handler(new LoggingHandler(LogLevel.INFO));
        try {
            future = bootstrap.bind(port).sync();
            // todo 将建立的连接放到NodeMap里
            log.info("Server listening at port :{}" , port);
        } catch (InterruptedException e) {
            log.info("Tcp Server bind error: {}.", e.getMessage());
            throw new RuntimeException(e);
        }

        log.info("NettyServer start and listening kcp connection.");
        UkcpServerBootstrap b = new UkcpServerBootstrap();
        b.group(CommVars.workGroup)
                .channel(UkcpServerChannel.class)
                .childHandler(kcpServerChannelInitilizer);
        ChannelOptionHelper.nodelay(b, true, 20, 2, true)
                .childOption(UkcpChannelOption.UKCP_MTU, 1400);
        ChannelFuture kcpFuture = null;
        try {
            // Start the server.
            kcpFuture = b.bind(8090).sync();
            log.info("KcpServer listening at port :{}" , 8090);
        } catch (InterruptedException e) {
            log.info("Kcp Server bind error: {}.", e.getMessage());
            throw new RuntimeException(e);
        }

        log.info("NettyServer start and listening multicast connection.");
        InetSocketAddress groupAddress = new InetSocketAddress(sysDept.getMulticastAddr(), sysDept.getMulticastPort());
        NetworkInterface ni = null;
        try {
            ni = NetworkInterface.getByInetAddress(InetAddress.getByName(host));
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        Enumeration<InetAddress> addresses = ni.getInetAddresses();
        InetAddress localAddr = null;
        while(addresses.hasMoreElements()){
            InetAddress address = addresses.nextElement();
            if(address instanceof Inet4Address) {
                localAddr = address;
                log.info("Multicast net：" + ni.getName());
            }
        }
        long channelWriteLimit = 16384l;
        long channelReadLimit = 16384l;
        String tmpWriteStr = sysConfigService.selectConfigByKey("trans.write.channelLimit");
        String tmpReadStr = sysConfigService.selectConfigByKey("trans.read.channelLimit");
        try {
            if(tmpWriteStr != null)
                channelWriteLimit = Long.parseLong(tmpWriteStr);
            if(tmpReadStr != null)
                channelReadLimit = Long.parseLong(tmpReadStr);
        } catch (NumberFormatException e) {
            log.error("【配置】系统参数配置出错");
        }
        long writeLimit = channelWriteLimit;
        long readLimit = channelReadLimit;

        log.info("[load local address] - IPV4 - {}", localAddr);
        Bootstrap multiBoot = new Bootstrap();
        multiBoot.group(CommVars.workGroup)
                .channelFactory(new ChannelFactory<NioDatagramChannel>() {
                    @Override
                    public NioDatagramChannel newChannel() {
                        // 指定协议簇为 IPv4
                        return new NioDatagramChannel(InternetProtocolFamily.IPv4);
                    }
                })
                .localAddress(localAddr, groupAddress.getPort())
                .option(ChannelOption.IP_MULTICAST_IF, ni)
                .option(ChannelOption.IP_MULTICAST_ADDR, localAddr)
                .option(ChannelOption.SO_REUSEADDR, true)
                .option(ChannelOption.SO_RCVBUF, 2048 * 1024)
                .option(ChannelOption.SO_SNDBUF, 1024 * 1024)
                .option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(1024 * 1024))
                .handler(new ChannelInitializer<NioDatagramChannel>() {
                    @Override
                    protected void initChannel(NioDatagramChannel ch) throws Exception {
                        // TODO Auto-generated method stub
                        ch.pipeline().addLast(GlobalTraffic.getInstance())
                                .addLast(new ChannelTrafficShapingHandler(writeLimit, readLimit, 1))
                                .addLast(new LengthFieldBasedFrameDecoder(14000, 0,4,0,4))
                                .addLast(new LengthFieldPrepender(4))
                                .addLast(new DatagramPacketCodec(groupAddress))
                                .addLast("msgCryptCodec",new PlainCryptMsgPackCodec())
                                .addLast("msgPackDecoder",new MsgPackDecoder())
                                .addLast("msgPackEncoder",new MsgPackEncoder())
                                .addLast("PlainMsgCodec", plainMsgCodec)
                                .addLast("fileCodec", fileMsgCodec);
                    }
                });
        NioDatagramChannel multiChannel = null;
        try {
            multiChannel = (NioDatagramChannel)multiBoot.bind(groupAddress.getPort()).sync().channel();
            multiChannel.joinGroup(groupAddress, ni).sync();
            log.info("Multicast Server start over");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Channel broadChannel = null;
        // 单向传输和广播传输端口一致
        log.info("NettyServer start and listening boardcast connection.");
        int boardPort = sysDept.getBoardcastPort();
        Bootstrap broadBoot = new Bootstrap();
        broadBoot.group(CommVars.workGroup)
                .channelFactory(new ChannelFactory<NioDatagramChannel>() {
                    @Override
                    public NioDatagramChannel newChannel() {
                        // 指定协议簇为 IPv4
                        return new NioDatagramChannel(InternetProtocolFamily.IPv4);
                    }
                })
                .option(ChannelOption.SO_BROADCAST, true)
                .option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(1024 * 1024))
                .handler(new ChannelInitializer<NioDatagramChannel>() {
                    @Override
                    protected void initChannel(NioDatagramChannel ch) throws Exception {
                        // TODO Auto-generated method stub
                        ch.pipeline().addLast(GlobalTraffic.getInstance())
                                .addLast(new ChannelTrafficShapingHandler(writeLimit, readLimit, 1))
                                .addLast(new LengthFieldBasedFrameDecoder(14000, 0,4,0,4))
                                .addLast(new LengthFieldPrepender(4))
                                .addLast(new DatagramPacketCodec(null))
                                .addLast("msgCryptCodec",new PlainCryptMsgPackCodec())
                                .addLast("msgPackDecoder",new MsgPackDecoder())
                                .addLast("msgPackEncoder",new MsgPackEncoder())
                                .addLast("PlainMsgCodec", plainMsgCodec)
                                .addLast("fileCodec", fileMsgCodec);
                    }
                });
        try {
            broadChannel = broadBoot.bind(boardPort).sync().channel();
            log.info("Boardcast Server start over");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            future.channel().closeFuture().sync();
            kcpFuture.channel().closeFuture().sync();
            multiChannel.closeFuture().sync();
            broadChannel.closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("Server close error, err msg : {} ", e.getMessage());
        }
    }

    @PreDestroy
    public void destory() {
        CommVars.bossGroup.shutdownGracefully();
        CommVars.workGroup.shutdownGracefully();
        log.info("Server stoped!");
    }

}
